www.gusucode.com > VC++ 实现标题栏背景颜色渐变效果-源码程序 > VC++ 实现标题栏背景颜色渐变效果-源码程序/code/PaintCap.cpp
//////////////////////////////////////////////////////////////// // 1997 Microsoft Systems Journal. // If this program works, it was written by Paul DiLascia. // If not, I don't know who wrote it. // download by http://www.NewXing.com #include "StdAfx.h" #include "PaintCap.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif //////////////// // Class used to get the caption rectangle of a window in window coords. // This is the area of the title bar inside the window frame, including // the icon and min/max/close buttons. // class CCaptionRect : public CRect { public: CCaptionRect(const CWnd& wnd); // use reference to deny NULL ptr }; IMPLEMENT_DYNAMIC(CCaptionPainter, CSubclassWnd); CCaptionPainter::CCaptionPainter() { Invalidate(); } CCaptionPainter::~CCaptionPainter() { } ////////////////// // Install caption handler. nPaintMsg is message I will send too frame // when its caption needs painting. // BOOL CCaptionPainter::Install(CFrameWnd* pFrameWnd,UINT nPaintMsg,BOOL bMod) { ASSERT_KINDOF(CFrameWnd, pFrameWnd); m_nPaintMsg = nPaintMsg; m_bModified=bMod; return HookWindow(pFrameWnd); } ////////////////// // Message handler handles caption-related messages // LRESULT CCaptionPainter::WindowProc(UINT msg, WPARAM wp, LPARAM lp) { switch (msg) { case WM_NCPAINT: OnNcPaint(HRGN(wp)); return 0; case WM_NCACTIVATE: return OnNcActivate(wp); case WM_SETTEXT: OnSetText((LPCTSTR)lp); return 0; case WM_SYSCOLORCHANGE: case WM_SETTINGCHANGE: Invalidate(); m_pWndHooked->SendMessage(m_nPaintMsg, 0, 0L); // return 0; } // I don't handle it: pass along return CSubclassWnd::WindowProc(msg, wp, lp); } ///////////////// // Handle WM_NCPAINT for main window // void CCaptionPainter::OnNcPaint(HRGN hRgn) { ASSERT_VALID(m_pWndHooked); CWnd& wnd = *m_pWndHooked; CCaptionRect rc(wnd); // caption rectangle in window coords CRect rcWin; // window rect wnd.GetWindowRect(&rcWin); // .. get window rect rc += rcWin.TopLeft(); // convert caption rect to screen coords // Don't bother painting if the caption doesn't lie within the region. // if ((WORD)hRgn > 1 && !::RectInRegion(hRgn, &rc)) { Default(); // just do default thing return; // and quit } // Exclude caption from update region // HRGN hRgnCaption = ::CreateRectRgnIndirect(&rc); HRGN hRgnNew = ::CreateRectRgnIndirect(&rc); if ((WORD)hRgn > 1) { // wParam is a valid region: subtract caption from it ::CombineRgn(hRgnNew, hRgn, hRgnCaption, RGN_DIFF); } else { // wParam is not a valid region: create one that's the whole // window minus the caption bar HRGN hRgnAll = ::CreateRectRgnIndirect(&rcWin); CombineRgn(hRgnNew, hRgnAll, hRgnCaption, RGN_DIFF); DeleteObject(hRgnAll); } // Call Windows to do WM_NCPAINT with altered update region // MSG& msg = AfxGetThreadState()->m_lastSentMsg; WPARAM savewp = msg.wParam; // save original wParam msg.wParam = (WPARAM)hRgnNew; // set new region for DefWindowProc Default(); DeleteObject(hRgnCaption); // clean up DeleteObject(hRgnNew); // ... msg.wParam = savewp; // restore original wParam PaintCaption(); // Now paint my special caption } ////////////////// // Handle WM_NCACTIVATE for main window // BOOL CCaptionPainter::OnNcActivate(BOOL bActive) { ASSERT_VALID(m_pWndHooked); CFrameWnd& frame = *((CFrameWnd*)m_pWndHooked); ASSERT_KINDOF(CFrameWnd, &frame); // Mimic MFC kludge to stay active if WF_STAYACTIVE bit is on if (frame.m_nFlags & WF_STAYACTIVE) bActive = TRUE; if (!frame.IsWindowEnabled()) // but not if disabled bActive = FALSE; if (bActive==m_bActive) return TRUE; // nothing to do // In case this is a MDI app, manually activate/paint active MDI child // window, because Windows won't do it if parent frame is invisible. // Must do this BEFORE calling Default, or it will not work. // CFrameWnd* pActiveFrame = frame.GetActiveFrame(); if (pActiveFrame!=&frame) { pActiveFrame->SendMessage(WM_NCACTIVATE,bActive); pActiveFrame->SendMessage(WM_NCPAINT); } // Turn WS_VISIBLE off before calling DefWindowProc, // so DefWindowProc won't paint and thereby cause flicker. // DWORD dwStyle = frame.GetStyle(); if (dwStyle & WS_VISIBLE) ::SetWindowLong(frame, GWL_STYLE, (dwStyle & ~ WS_VISIBLE)); MSG& msg = AfxGetThreadState()->m_lastSentMsg; msg.wParam = bActive; Default(); if (dwStyle & WS_VISIBLE) ::SetWindowLong(frame, GWL_STYLE, dwStyle); // At this point, nothing has happened (since WS_VISIBLE was off). // Now it's time to paint. // m_bActive = bActive; // update state frame.SendMessage(WM_NCPAINT); // paint non-client area (frame too) return TRUE; // done OK } ////////////////// // Handle WM_SETTEXT for main window // void CCaptionPainter::OnSetText(LPCTSTR lpText) { ASSERT_VALID(m_pWndHooked); CWnd& wnd = *m_pWndHooked; // Turn WS_VISIBLE style off before calling Windows to // set the text, then turn it back on again after. // DWORD dwStyle = wnd.GetStyle(); if (dwStyle & WS_VISIBLE) SetWindowLong(wnd.m_hWnd, GWL_STYLE, dwStyle & ~ WS_VISIBLE); Default(); if (dwStyle & WS_VISIBLE) SetWindowLong(wnd.m_hWnd, GWL_STYLE, dwStyle); wnd.SendMessage(WM_NCPAINT); // paint non-client (frame) Invalidate(); // force new bitmap PaintCaption(); } ////////////////// // Paint custom caption. Flag tells whether active or not. Just blast the // bitmap to the title bar, but not if minimized (iconic). // void CCaptionPainter::PaintCaption() { ASSERT(m_pWndHooked); CWnd& wnd = *m_pWndHooked; if (wnd.IsIconic()) return; // Get caption DC and rectangle CWindowDC dcWin(&wnd); // window DC CDC dc; // memory DC dc.CreateCompatibleDC(&dcWin); // ...create it CCaptionRect rc(wnd); // get caption rectangle if (rc.Size() != m_szCaption) { // if size changed: m_bmCaption[0].DeleteObject(); // invalidate bitmaps m_bmCaption[1].DeleteObject(); // ... m_szCaption = rc.Size(); // update new size } // Get active/inactive bitmap & determine if needs to be regenerated CBitmap& bm = m_bmCaption[m_bActive!=0]; // get bitmap BOOL bPaintIt = FALSE; // paint anew? if (!(HBITMAP)bm) { // no bitmap: bm.CreateCompatibleBitmap(&dcWin, rc.Width(), rc.Height()); // create one bPaintIt = TRUE; // and paint it } CBitmap* pOldBitmap = dc.SelectObject(&bm); // select bitmap into memory DC // If bitmap needs painting, call client to do it. if (bPaintIt) { PAINTCAP pc; pc.m_pDC = &dc; pc.m_szCaption = rc.Size(); pc.m_bActive=m_bActive; if(m_nPaintMsg) wnd.SendMessage(m_nPaintMsg, m_bActive, (LPARAM)&pc); else DrawNormalCaption(pc); } // blast bits to screen dcWin.BitBlt(rc.left,rc.top,rc.Width(),rc.Height(),&dc,0,0,SRCCOPY); dc.SelectObject(pOldBitmap); // restore DC } void CCaptionPainter::DrawNormalCaption(const PAINTCAP& pc) { ASSERT(m_pWndHooked); CWnd& wnd = *m_pWndHooked; UINT uFlags = DC_TEXT|DC_ICON; if(pc.m_bActive)uFlags|=DC_ACTIVE; CRect rc(CPoint(0,0),pc.m_szCaption); ::DrawCaption(wnd,*pc.m_pDC,&rc,uFlags); DrawButtons(pc); } //////////////// // Draw caption icon. Returns width of icon. // int CCaptionPainter::DrawIcon(const PAINTCAP& pc) { ASSERT(m_pWndHooked); CWnd& wnd = *m_pWndHooked; // Within the basic button rectangle, Windows 95 uses a 1 or 2 pixel border // Icon has 2 pixel border on left, 1 pixel on top/bottom, 0 right // int cxIcon = GetSystemMetrics(SM_CXSIZE); CRect rc(0, 0, cxIcon, GetSystemMetrics(SM_CYSIZE)); rc.DeflateRect(0,1); rc.left += 2; DrawIconEx(pc.m_pDC->m_hDC, rc.left, rc.top, (HICON)GetClassLong(wnd.m_hWnd, GCL_HICONSM), rc.Width(), rc.Height(), 0, NULL, DI_NORMAL); return cxIcon; } //////////////// // Draw min, max/restore, close buttons. // Returns total width of buttons drawn. // int CCaptionPainter::DrawButtons(const PAINTCAP& pc) { ASSERT(m_pWndHooked); CWnd& wnd = *m_pWndHooked; DWORD dwStyle = wnd.GetStyle(); if (!(dwStyle & WS_CAPTION)) return 0; ASSERT(pc.m_pDC); CDC& dc = *pc.m_pDC; int cxIcon = GetSystemMetrics(SM_CXSIZE); int cyIcon = GetSystemMetrics(SM_CYSIZE); // Draw caption buttons. These are all drawn inside a rectangle // of dimensions SM_CXSIZE by SM_CYSIZE CRect rc(0, 0, cxIcon, cyIcon); rc += CPoint(pc.m_szCaption.cx-cxIcon, 0); // move right // Close box has a 2 pixel border on all sides but left, which is zero rc.DeflateRect(0,2); rc.right -= 2; dc.DrawFrameControl(&rc, DFC_CAPTION, DFCS_CAPTIONCLOSE); // Max/restore button is like close box; just shift rectangle left // Also does help button, if any. BOOL bMaxBox = dwStyle & WS_MAXIMIZEBOX; if (bMaxBox || (wnd.GetExStyle() & WS_EX_CONTEXTHELP)) { rc -= CPoint(cxIcon, 0); dc.DrawFrameControl(&rc, DFC_CAPTION, bMaxBox ? (wnd.IsZoomed() ? DFCS_CAPTIONRESTORE : DFCS_CAPTIONMAX) : DFCS_CAPTIONHELP); } // Minimize button has 2 pixel border on all sides but right. if (dwStyle & WS_MINIMIZEBOX) { rc -= CPoint(cxIcon-2,0); dc.DrawFrameControl(&rc, DFC_CAPTION, DFCS_CAPTIONMIN); } return pc.m_szCaption.cx - rc.left - 2; } ////////////////// // CCaptionRect Constructor computes caption rectangle in window coords. // CCaptionRect::CCaptionRect(const CWnd& wnd) { // Get size of frame around window DWORD dwStyle = wnd.GetStyle(); CSize szFrame = (dwStyle & WS_THICKFRAME) ? CSize(GetSystemMetrics(SM_CXSIZEFRAME), GetSystemMetrics(SM_CYSIZEFRAME)) : CSize(GetSystemMetrics(SM_CXFIXEDFRAME), GetSystemMetrics(SM_CYFIXEDFRAME)); int cxIcon = GetSystemMetrics(SM_CXSIZE); // width of caption icon/button // Compute rectangle wnd.GetWindowRect(this); // window rect in screen coords *this -= CPoint(left, top); // shift origin to (0,0) left += szFrame.cx; // frame right -= szFrame.cx; // frame top += szFrame.cy; // top = end of frame bottom = top + GetSystemMetrics(SM_CYCAPTION) // height of caption - GetSystemMetrics(SM_CYBORDER); // minus gray shadow border } ////////////////// // Helper function to compute the luminosity for an RGB color. // Measures how bright the color is. I use this so I can draw the caption // text using the user's chosen color, unless it's too dark. See MSDN for // definition of luminosity and how to compute it. // int CCaptionPainter::GetLuminosity(COLORREF color) { #define HLSMAX 240 // This is what Display Properties uses #define RGBMAX 255 // max r/g/b value is 255 int r = GetRValue(color); int g = GetGValue(color); int b = GetBValue(color); int rgbMax = max( max(r,g), b); int rgbMin = min( min(r,g), b); return (((rgbMax+rgbMin) * HLSMAX) + RGBMAX ) / (2*RGBMAX); } #define COLOR_WHITE RGB(255,255,255) #define COLOR_BLACK RGB(0,0,0) #define NCOLORSHADES 64 // this many shades in gradient ////////////////// // Helper to paint rectangle with a color. // void CCaptionPainter::PaintRect(CDC& dc, int x, int y, int w, int h, COLORREF color) { CBrush brush(color); CBrush* pOldBrush = dc.SelectObject(&brush); dc.PatBlt(x, y, w, h, PATCOPY); dc.SelectObject(pOldBrush); } ////////////////// // Paint custom caption. // This is the function that actually does the shading. It creates a // bitmap that's used to paint the caption. It looks horrible, but it's // just a lot of bit-twiddling GDI stuff. // void CCaptionPainter::PaintMyCaption(WPARAM bActive, LPARAM lParam, CString m_strTitle) { if (lParam == 0) { // lParam = 0 means system setting change: invalidate fonts. m_fontCaption.DeleteObject(); return; } const PAINTCAP& pc = *((PAINTCAP*)lParam); ASSERT(pc.m_pDC); CDC& dc = *pc.m_pDC; int cxCap = pc.m_szCaption.cx; int cyCap = pc.m_szCaption.cy; COLORREF clrFrom; // the beginning color COLORREF clrTo; // the ending color // These are the default colors in Windows(R) 98 // Modify them to suit your needs. if (bActive) { // Active caption clrFrom = GetSysColor(COLOR_ACTIVECAPTION); clrTo = RGB(16, 132, 208); } else { // Inactive caption clrFrom = GetSysColor(COLOR_INACTIVECAPTION); clrTo = RGB(184, 180, 184); } // Get the intensity values for the ending color int r1 = GetRValue(clrTo); // red int g1 = GetGValue(clrTo); // green int b1 = GetBValue(clrTo); // blue // Get the intensity values for the begining color int r2 = GetRValue(clrFrom); // red int g2 = GetGValue(clrFrom); // green int b2 = GetBValue(clrFrom); // blue int x = 5*cxCap/6; // start 5/6 of the way right int w = x; // width of area to shade int xDelta= max(w/NCOLORSHADES,1); // width of one shade band // Paint far right 1/6 of caption the background color PaintRect(dc, x, 0, cxCap-x, cyCap, clrTo); int r, g, b; while (x > xDelta) { x -= xDelta; if (r1 > r2) r = r1 - (r1-r2)*(w-x)/w; else r = r1 + (r2-r1)*(w-x)/w; if (g1 > g2) g = g1 - (g1-g2)*(w-x)/w; else g = g1 + (g2-g1)*(w-x)/w; if (b1 > b2) b = b1 - (b1-b2)*(w-x)/w; else b = b1 + (b2-b1)*(w-x)/w; // Paint bands right to left PaintRect(dc, x, 0, xDelta, cyCap, RGB(r, g, b)); } // Paint what's left of the caption with the beginning color PaintRect(dc, 0, 0, x, cyCap, clrFrom); // Use caption painter to draw icon and buttons int cxIcon = DrawIcon(pc); int cxButns = DrawButtons(pc); // Now draw text. First Create fonts if needed if (!m_fontCaption.m_hObject) CreateFonts(); CString dt=GetDocTitle(); CString s; // app title if(dt.IsEmpty()) s = " " + m_strTitle; else s = " " + m_strTitle + " - [" + dt + "]"; CRect rc(CPoint(0,0), pc.m_szCaption); // text rectangle rc.left += cxIcon+2; // start after icon rc.right -= cxButns; // don't draw past buttons dc.SetBkMode(TRANSPARENT); // draw on top of our shading // This is a trial and error value that sets the point // where the caption background is too light/dark in // order to display the text with white/black. #define LUMINOSITY_MARK 120 if (GetLuminosity(clrFrom) > LUMINOSITY_MARK) dc.SetTextColor(COLOR_BLACK); else dc.SetTextColor(COLOR_WHITE); CFont* pOldFont = dc.SelectObject(&m_fontCaption); dc.DrawText(s, &rc, DT_LEFT|DT_VCENTER|DT_SINGLELINE|DT_END_ELLIPSIS); // Restore DC dc.SelectObject(pOldFont); } ////////////////// // Helper function to build the fonts I need. // void CCaptionPainter::CreateFonts() { // Get current system caption font, just to get its size // NONCLIENTMETRICS ncm; ncm.cbSize = sizeof(ncm); VERIFY(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &ncm, 0)); m_fontCaption.CreateFontIndirect(&ncm.lfCaptionFont); } // Private MFC function only sets the title if it's different // extern void AFXAPI AfxSetWindowText(HWND, LPCTSTR); void CCaptionPainter::UpdateFrameTitle(HWND m_hWnd, CString m_strTitle) { CString s = (m_strTitle + " " + GetDocTitle()); AfxSetWindowText(m_hWnd,(LPCTSTR)s); } ////////////////// // Get doc title: I use full path name or "untitled" // CString CCaptionPainter::GetDocTitle() { static CString s; s.Empty(); CFrameWnd *mFrame = (CFrameWnd *)AfxGetMainWnd(); if(mFrame){ CFrameWnd *pFrame = mFrame->GetActiveFrame(); if(pFrame!=mFrame && pFrame){ pFrame->GetWindowText(s); if(!s.IsEmpty()){ if(m_bModified){ CDocument *pDoc = pFrame->GetActiveDocument(); if(pDoc){ if(pDoc->IsModified()&&s.GetAt(s.GetLength()-1)!='*')s+="*"; } } } } } return s; }